// Copyright 2016 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <dirent.h>
#include <sys/types.h>

#include "application/src/bootstrap/app.h"

#include <magenta/process.h>
#include <magenta/processargs.h>

#include "application/lib/app/connect.h"
#include "lib/ftl/functional/make_copyable.h"
#include "lib/ftl/logging.h"

namespace bootstrap {
namespace {

// We explicitly launch netstack because netstack registers itself as
// |/dev/socket|, which needs to happen eagerly, instead of being discovered
// via |/svc/net.Netstack|, which can happen asynchronously.
void LaunchNetstack(app::ServiceProvider* provider) {
  mx::channel h1, h2;
  mx::channel::create(0, &h1, &h2);
  provider->ConnectToService("net.Netstack", std::move(h1));
}

}  // namespace

constexpr char kDefaultLabel[] = "boot";
constexpr char kConfigDir[] = "/system/data/bootstrap/";

App::App()
    : application_context_(app::ApplicationContext::CreateFromStartupInfo()),
      env_host_binding_(this) {
  FTL_DCHECK(application_context_);

  Config config;
  char buf[PATH_MAX];
  if (strlcpy(buf, kConfigDir, PATH_MAX) >= PATH_MAX) {
    FTL_LOG(ERROR) << "Config directory path too long";
  } else {
    const size_t dir_len = strlen(buf);
    DIR* cfg_dir = opendir(kConfigDir);
    if (cfg_dir != NULL) {
      for (dirent* cfg = readdir(cfg_dir); cfg != NULL;
           cfg = readdir(cfg_dir)) {
        if (strcmp(".", cfg->d_name) == 0 || strcmp("..", cfg->d_name) == 0) {
          continue;
        }
        if (strlcat(buf, cfg->d_name, PATH_MAX) >= PATH_MAX) {
          FTL_LOG(WARNING) << "Could not read config file, path too long";
          continue;
        }
        config.ReadFrom(buf);
        buf[dir_len] = '\0';
      }
      closedir(cfg_dir);
    } else {
      FTL_LOG(WARNING) << "Could not open config directory" << kConfigDir;
    }
  }

  // Set up environment for the programs we will run.
  app::ApplicationEnvironmentHostPtr env_host;
  env_host_binding_.Bind(env_host.NewRequest());
  application_context_->environment()->CreateNestedEnvironment(
      std::move(env_host), env_.NewRequest(), env_controller_.NewRequest(),
      kDefaultLabel);
  env_->GetApplicationLauncher(env_launcher_.NewRequest());

  // Register services.
  for (auto& pair : config.TakeServices())
    RegisterSingleton(pair.first, std::move(pair.second));

  // Ordering note: The impl of CreateNestedEnvironment will resolve the
  // delegating app loader. However, since its call back to the env host won't
  // happen until the next (first) message loop iteration, we'll be set up by
  // then.
  RegisterAppLoaders(config.TakeAppLoaders());

  // Launch startup applications.
  for (auto& launch_info : config.TakeApps())
    LaunchApplication(std::move(launch_info));

  // TODO(abarth): Remove this hard-coded mention of netstack once netstack is
  // fully converted to using service namespaces.
  LaunchNetstack(&env_services_);
}

App::~App() {}

void App::RegisterSingleton(std::string service_name,
                            app::ApplicationLaunchInfoPtr launch_info) {
  env_services_.AddServiceForName(
      ftl::MakeCopyable([
        this, service_name, launch_info = std::move(launch_info),
        controller = app::ApplicationControllerPtr()
      ](mx::channel client_handle) mutable {
        FTL_VLOG(2) << "Servicing singleton service request for "
                    << service_name;
        auto it = services_.find(launch_info->url);
        if (it == services_.end()) {
          FTL_VLOG(1) << "Starting singleton " << launch_info->url
                      << " for service " << service_name;
          app::Services services;
          auto dup_launch_info = app::ApplicationLaunchInfo::New();
          dup_launch_info->url = launch_info->url;
          dup_launch_info->arguments = launch_info->arguments.Clone();
          dup_launch_info->service_request = services.NewRequest();
          env_launcher_->CreateApplication(std::move(dup_launch_info),
                                           controller.NewRequest());
          controller.set_connection_error_handler(
              [ this, url = launch_info->url, &controller ] {
                FTL_LOG(ERROR) << "Singleton " << url << " died";
                controller.reset();  // kills the singleton application
                services_.erase(url);
              });

          std::tie(it, std::ignore) =
              services_.emplace(launch_info->url, std::move(services));
        }

        it->second.ConnectToService(service_name, std::move(client_handle));
      }),
      service_name);
}

void App::RegisterAppLoaders(Config::ServiceMap app_loaders) {
  app_loader_ = std::make_unique<DelegatingApplicationLoader>(
      std::move(app_loaders), env_launcher_.get(),
      application_context_
          ->ConnectToEnvironmentService<app::ApplicationLoader>());

  env_services_.AddService<app::ApplicationLoader>(
      [this](fidl::InterfaceRequest<app::ApplicationLoader> request) {
        app_loader_bindings_.AddBinding(app_loader_.get(), std::move(request));
      });
}

void App::LaunchApplication(app::ApplicationLaunchInfoPtr launch_info) {
  FTL_VLOG(1) << "Bootstrapping application " << launch_info->url;
  env_launcher_->CreateApplication(std::move(launch_info), nullptr);
}

void App::GetApplicationEnvironmentServices(
    fidl::InterfaceRequest<app::ServiceProvider> environment_services) {
  env_services_.AddBinding(std::move(environment_services));
}

}  // namespace bootstrap
